<template>
  <div class="upload">
    <el-upload
      :file-list="fileList"
      class="upload-demo"
      drag
      multiple
      :before-upload="beforeUpload"
      :accept="'.mp4'"
      :remove="handleRemove"
    >
      <el-icon class="el-icon--upload"><upload-filled /></el-icon>
      <div class="el-upload__text">拖动文件到这里或者 <em>点击上传</em></div>
      <template #tip>
        <div class="el-upload__tip">
          <!-- jpg/png files with a size less than 500kb -->
        </div>
      </template>
    </el-upload>
    <el-button
      type="primary"
      :disabled="fileList.length === 0 || !finishSlice"
      :loading="uploading"
      style="margin-top: 16px"
      @click="handleUpload"
    >
      {{ uploading ? "上传中" : "开始上传" }}
    </el-button>
    <!-- <el-progress
      :percent="Math.round((sliceProgress / sliceCount) * 100)"
      :status="sliceProgress === sliceCount ? 'success' : 'active'"
      v-if="showSliceProgress"
    />
    <el-progress
      :percent="Math.round((finishCount / sliceCount) * 100)"
      :status="finishCount === sliceCount ? 'success' : 'active'"
      v-if="showProgress"
    /> -->
  </div>
</template>

<script setup lang="ts">
import { UploadFilled } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import SparkMD5 from "spark-md5";
import { ref } from "vue";
import axios from "axios";
import { FILEUPLOADURL } from "@/config/urlConfig";
// import { createFFmpeg, fetchFile } from "@ffmpeg/ffmpeg";

const emit = defineEmits(["successUpload", "videoTime"]);

// 文件列表
const fileList = ref<File[]>([]);
// 上传状态
const uploading = ref<boolean>(false);
// 分片完成情况
const finishSlice = ref<boolean>(false);
// 完成上传的分片数量
const finishCount = ref<number>(0);
// 展示上传进度条
const showProgress = ref<boolean>(false);
// 切片数量
const sliceCount = ref<number>(0);
// 切片进度条
const sliceProgress = ref<number>(0);
// 上传失败的数量
const errorCount = ref<number>(0);
// 展示切片进度条
const showSliceProgress = ref<boolean>(false);
// 切片列表
let fileChunkList: any = [];
// 发送的切片数量
const sendCount = ref<number>(0);
// 文件类型
let filetype = "";
// 文件名
let filename = "";
// 文件hash值
let hash = "";

// const ffmpeg = createFFmpeg({
//   corePath: "/path/to/ffmpeg-core.js",
//   log: true
// });

// const convertVideo = async (file: File) => {
//   await ffmpeg.load();

//   ffmpeg.FS("writeFile", "input.mp4", await fetchFile(file));

//   await ffmpeg.run("-i", "input.mp4", "-c:v", "libx264", "output.mp4");

//   const data = ffmpeg.FS("readFile", "output.mp4");
//   const blob = new Blob([data.buffer], { type: "video/mp4" });
//   console.log(URL.createObjectURL(blob));
// };

const beforeUpload = (file: File) => {
  // 获取视频时间
  var audio = new Audio(URL.createObjectURL(file)); // 创建音频对象
  const loadedmetadataFun = () => {
    const duration = audio.duration; // 单位：秒
    console.log("视频时长为:", duration);

    emit("videoTime", duration.toFixed(2));
    audio.removeEventListener("loadedmetadata", loadedmetadataFun);
  };
  audio.addEventListener("loadedmetadata", loadedmetadataFun);

  // convertVideo(file);
  ElMessage.info("开始文件切片");
  // 显示切片进度条
  showSliceProgress.value = true;
  // 文件添加到文件列表 这里只展示单文件上传
  fileList.value = [file];
  // 一些参数的初始化
  fileChunkList = [];
  finishSlice.value = false;
  finishCount.value = 0;
  sliceProgress.value = 0;
  showProgress.value = false;
  sliceCount.value = 0;
  errorCount.value = 0;

  return new Promise((resolve, reject) => {
    console.log(resolve, reject);
    // 初始化md5工具对象
    const spark = new SparkMD5.ArrayBuffer();
    // 用于读取文件计算md5
    const fileReader = new FileReader();
    // 这里是依据.来对文件和类型进行分割
    let fileInfo = file.name.split(".");
    filename = fileInfo[0];
    // 最后一个.之前的内容都应该认定为文件名称
    if (fileInfo.length > 1) {
      filetype = fileInfo[fileInfo.length - 1];
      for (let i = 1; i < fileInfo.length - 1; i++) {
        filename = filename + "." + fileInfo[i];
      }
    }
    // 这里开始做切片
    // 设置切片大小 可以根据实际情况设置
    const chunkSize = 1024 * 1024 * 1;
    // 计算出切片数量
    sliceCount.value = Math.ceil(file.size / chunkSize);
    let curChunk = 0;
    // 切片操作的实际方法【定义】
    const sliceNext = () => {
      // 使用slice方法进行文件切片
      const chunkFile = file.slice(curChunk, curChunk + chunkSize);
      // 读取当前切片文件流【这里会触发onload方法】
      fileReader.readAsArrayBuffer(chunkFile);
      // 加入切片列表
      fileChunkList.push({
        // 切片文件信息
        chunk: chunkFile,
        // 文件名
        filename: filename,
        // 分片索引 这里直接借助sliceProgress来实现
        seq: sliceProgress.value + 1,
        // 文件类型
        type: filetype,
        // 状态信息 用于标识是否上传成功
        status: false
      });
      // 切片完成变量自增
      sliceProgress.value++;
    };

    // 进入方法需要进行首次切片操作
    sliceNext();

    // 读取文件流时会触发onload方法
    fileReader.onload = (e: any) => {
      // 将文件流加入计算md5
      spark.append(e.target.result);
      // 修改切片位移
      curChunk += chunkSize;
      // 说明还没到达最后一个切片 继续切
      if (sliceProgress.value < sliceCount.value) {
        sliceNext();
      } else {
        // 说明切片完成了
        finishSlice.value = true;
        // 读取文件hash值
        hash = spark.end();
        ElMessage.success("文件分片完成");
        // 将哈希值作为其中一个属性 写入到分片列表中
        fileChunkList.forEach((content: any) => {
          content.hash = hash;
        });
      }
    };
  });
};

// 开始执行上传切片逻辑
const startUpload = () => {
  return new Promise((resolve, reject) => {
    console.log(reject, resolve);
    const next = () => {
      // 递归出口 分片上传完毕
      if (finishCount.value + errorCount.value >= sliceCount.value) {
        ElMessage.success("分成上传完成");
        return;
      }
      // 记录当前遍历位置
      let cur = sendCount.value++;
      // 说明越界了 直接退出
      if (cur >= sliceCount.value) {
        return;
      }
      // 获取分片信息
      let content = fileChunkList[cur];
      // 已经上传过了 直接跳过【可用于断点续传】
      if (content.status === true) {
        if (finishCount.value + errorCount.value < sliceCount.value) {
          next();
          return;
        }
      }
      // 开始填充上传数据 这里需要使用FormData来存储信息
      const formData = new FormData();
      formData.append("file", content.chunk);
      formData.append("hash", content.hash);
      formData.append("filename", content.filename);
      formData.append("seq", content.seq);
      formData.append("type", content.type);
      // 开始上传
      axios
        .post(FILEUPLOADURL + "/video", formData)
        .then((res: any) => {
          // 接收回调信息
          if (res.data.code == 200) {
            // 成功计数 并设置分片上传状态
            finishCount.value += 1;
            content.status = true;
          } else {
            // 失败计数
            errorCount.value += 1;
          }
          // 说明完成最后一个分片上传但上传期间出现错误
          if (
            errorCount.value !== 0 &&
            errorCount.value + finishCount.value === sliceCount.value
          ) {
            ElMessage.error("上传发生错误，请重传");
            showProgress.value = false;
            uploading.value = false;
          }
          // 说明还有分片未上传 需要继续递归
          if (finishCount.value + errorCount.value < sliceCount.value) {
            next();
          }
          // 说明所有分片上传成功了 发起合并操作
          if (finishCount.value === sliceCount.value) {
            merge();
          }
        })
        .catch(error => {
          // 对于图中发生的错误需要捕获并记录
          errorCount.value += 1;
          if (
            errorCount.value !== 0 &&
            errorCount.value + finishCount.value === sliceCount.value
          ) {
            ElMessage.error("上传发生错误，请重传！");
            showProgress.value = false;
            uploading.value = false;
          }
          // 当前分片上传失败不应影响下面的分片
          if (finishCount.value + errorCount.value < sliceCount.value) {
            next();
          }
          console.log(error);
        });
    };
    // 只允许同时10个任务在等待
    while (sendCount.value < 10 && sendCount.value < sliceCount.value) {
      next();
    }
  });
};

const merge = () => {
  ElMessage.success("上传成功，等待服务器合并文件");
  console.log("type", filetype);
  // 发起合并请求 传入文件hash值、文件类型、文件名
  axios
    .post(FILEUPLOADURL + "/merge", {
      hash: hash,
      type: filetype,
      filename: filename
    })
    .then((res: any) => {
      const data = res.data;
      if (res.data.code == 200) {
        console.log(res, "成功");
        ElMessage.success("上传成功");
        // 获取上传成功的文件地址
        console.log(data.data.msg);
        emit("successUpload", data.data.msg);
        // 其他业务操作...
      } else {
        ElMessage.error(data.msg);
      }
      uploading.value = false;
    })
    .catch(e => {
      console.log(e);
      ElMessage.error("发生错误了");
      uploading.value = false;
    });
};

const handleRemove = (file: File) => {
  const index = fileList.value.indexOf(file);
  const newFileList = fileList.value.slice();
  // let hash = "";
  newFileList.splice(index, 1);
  fileList.value = newFileList;
  // 取消之后需要进行相关变量的重新初始化
  fileChunkList = [];
  finishSlice.value = false;
  finishCount.value = 0;
  sliceProgress.value = 0;
  showProgress.value = false;
  sliceCount.value = 0;
  errorCount.value = 0;
};

const handleUpload = async () => {
  if (!finishSlice.value) {
    ElMessage.error("请等待切片上传完成");
    return;
  }
  // 进度条变更
  showSliceProgress.value = false;
  // 先检查是否已经上传过
  axios
    .get(FILEUPLOADURL + "/check?hash=" + hash)
    .then((res: any) => {
      const data = res.data;
      if (res.data.code == 200) {
        ElMessage.success("文件已经上传过");
        console.log(data.content);
      } else {
        // 开始上传逻辑 相关变量状态更迭
        uploading.value = true;
        // 这里主要是服务于断点续传 避免重复上传已成功分块
        sliceCount.value -= finishCount.value;
        errorCount.value = 0;
        finishCount.value = 0;
        sendCount.value = 0;
        showProgress.value = true;
        // 调用上面写好的上传逻辑
        startUpload();
      }
    })
    .catch(error => {
      ElMessage.error("发生异常");
      console.log(error);
    });
};
</script>

<style lang="scss" scoped>
.upload {
  width: 100%;
  margin-top: 30px;
}

.video {
  // width: 400px;
  width: 640px;
  height: 360px;
  // background-color: #000;
}
</style>
